"""PostGIS空间关系教学演示页面

Streamlit应用页面，展示PostGIS空间数据可视化和空间关系分析。
"""

import streamlit as st
from typing import Optional, List, Dict, Any
import traceback
import pandas as pd
import os
import zipfile
import glob
import shutil

from ..database.connection import DatabaseManager
from ..repositories.spatial_repository import SpatialRepository
from ..services.spatial_visualization import SpatialVisualization


def init_postgis_components():
    """初始化PostGIS组件
    
    Returns:
        tuple: (spatial_repo, spatial_viz) 或 (None, None) 如果初始化失败
    """
    try:
        # 初始化数据库管理器
        db_manager = DatabaseManager()
        
        # 初始化空间数据仓储
        spatial_repo = SpatialRepository(db_manager)
        
        # 初始化空间数据可视化
        spatial_viz = SpatialVisualization()
        
        return spatial_repo, spatial_viz
    except Exception as e:
        st.error(f"初始化PostGIS组件失败: {str(e)}")
        st.error("请确保：")
        st.error("1. PostgreSQL/PostGIS数据库正在运行")
        st.error("2. 数据库连接配置正确")
        st.error("3. 已执行PostGIS示例数据导入脚本")
        return None, None


def render_postgis_demo():
    """渲染PostGIS教学演示页面"""
    st.title("🌍 PostGIS空间关系教学演示系统")
    
    # 页面说明
    st.markdown("""
    本系统演示PostGIS中的空间关系：
    - 🎯 **点在面内关系**: 查找包含在指定面要素内的所有点要素
    - 🏫 **区域内教育机构查询**: 查找指定区域内的教育机构
    """)

    with st.expander("📝 GIS 数据 (Geographic Information System Data)说明"):
        st.markdown(
            """
            GIS 数据包括矢量数据（点、线、面）和栅格数据，用于表示地理空间信息，在水利工程中广泛应用于流域分析、洪水模拟、管网管理等。
            ##  存储解决方案
            ### 空间数据库：
            - PostGIS: 作为 PostgreSQL 的空间扩展，是 GIS 领域事实上的标准，功能强大，支持复杂的空间查询和分析。
            - SpatiaLite: 基于 SQLite 的轻量级空间数据库，适合移动应用或桌面级的 GIS 需求。
            - Elasticsearch with Geo-Spatial capabilities: 适合对海量地理位置数据进行快速搜索和聚合，常用于地理位置搜索和可视化。
            ### 大数据文件系统：
            -Apache Parquet/Apache ORC: 柱状存储格式，能高效压缩和查询地理空间数据，适合与 Spark 等大数据框架集成。
            ##  处理与分析组件
            ### Python 生态系统：
            - Geopandas: 扩展了 Pandas 的功能，可以处理地理空间数据，并与多种 GIS 格式（如 Shapefile, GeoJSON）无缝对接。
            - Fiona: 读写地理空间数据文件的库。
            - Pyproj: 进行地理坐标系转换的库。
            - Shapely: 用于进行几何对象操作和分析。
            - Rasterio: 读写栅格数据的库。
            ### GIS 软件库：
            - GDAL/OGR: 强大的地理空间数据转换和处理库，支持几乎所有的地理空间数据格式。
            - PROJ: 坐标转换和投影库，Geopandas 和 GDAL 的底层依赖。
            ### 大数据处理：
            - GeoSpark (Apache Sedona): 基于 Apache Spark 的空间数据处理框架，支持对大规模空间数据进行分布式查询和分析。
            - PostGIS on Hadoop/Spark: 将 PostGIS 的空间分析能力与大数据平台相结合。
            ### 可视化库：
            - Folium: 基于 Leaflet.js 的 Python 地图可视化库。
            - Geoplot: 基于 Matplotlib 和 Cartopy 的地理空间可视化库。
            """
        )
    
    # 初始化组件
    spatial_repo, spatial_viz = init_postgis_components()
    
    if not spatial_repo or not spatial_viz:
        st.stop()
    
    # 侧边栏选择演示类型
    st.sidebar.title("📋 演示选项")
    demo_type = st.sidebar.selectbox(
        "选择演示类型",
        [
            "总览地图",
            "点在面内关系",
            "区域内教育机构查询",
            "区域GIS分析"
        ]
    )
    
    try:
        if demo_type == "总览地图":
            render_overview_demo(spatial_repo, spatial_viz)
        elif demo_type == "点在面内关系":
            render_points_in_polygon_demo(spatial_repo, spatial_viz)
        elif demo_type == "区域内教育机构查询":
            render_educational_institutions_demo(spatial_repo, spatial_viz)
        elif demo_type == "区域GIS分析":
            render_region_gis_analysis_demo(spatial_repo, spatial_viz)
            
    except Exception as e:
        st.error(f"演示过程中发生错误: {str(e)}")
        with st.expander("查看详细错误信息"):
            st.code(traceback.format_exc())


def render_overview_demo(spatial_repo: SpatialRepository, 
                        spatial_viz: SpatialVisualization):
    """渲染总览演示
    
    Args:
        spatial_repo: 空间数据仓储
        spatial_viz: 空间数据可视化
    """
    st.header("🗺️ 空间数据总览")
    
    # 获取所有数据
    with st.spinner("正在加载空间数据..."):
        points_data = spatial_repo.get_all_points()
        polygons_data = spatial_repo.get_all_polygons()
        regions_data = spatial_repo.get_all_regions()
    
    # 显示数据统计
    col1, col2, col3 = st.columns(3)
    with col1:
        st.metric("点要素数量", len(points_data))
    with col2:
        st.metric("面要素数量", len(polygons_data))
    with col3:
        st.metric("区域数量", len(regions_data))
    
    # 渲染总览地图
    spatial_viz.render_overview_map(points_data, polygons_data, regions_data)
    
    # 显示数据详情
    with st.expander("📊 数据详情"):
        tab1, tab2, tab3 = st.tabs(["点要素", "面要素", "区域"])
        
        with tab1:
            if points_data:
                for point in points_data:
                    st.write(f"• **{point['name']}** ({point.get('category', '未分类')})")
            else:
                st.info("暂无点要素数据")
        
        with tab2:
            if polygons_data:
                for polygon in polygons_data:
                    area_text = f" - {polygon['area']:,} 平方米" if polygon.get('area') else ""
                    st.write(f"• **{polygon['name']}** ({polygon.get('category', '未分类')}){area_text}")
            else:
                st.info("暂无面要素数据")
        
        with tab3:
            if regions_data:
                for region in regions_data:
                    st.write(f"• **{region['name']}** ({region.get('region_type', '未分类')}) - 级别{region.get('level', 0)}")
            else:
                st.info("暂无区域数据")


def render_points_in_polygon_demo(spatial_repo: SpatialRepository, 
                                 spatial_viz: SpatialVisualization):
    """渲染点在面内关系演示
    
    Args:
        spatial_repo: 空间数据仓储
        spatial_viz: 空间数据可视化
    """
    st.header("🎯 点在面内空间关系演示")
    
    # 获取所有面要素用于选择
    with st.spinner("正在加载面要素数据..."):
        polygons_data = spatial_repo.get_all_polygons()
    
    if not polygons_data:
        st.warning("没有找到面要素数据，请先导入示例数据")
        return
    
    # 选择面要素
    polygon_options = {f"{p['name']} ({p.get('category', '未分类')})": p['id'] 
                      for p in polygons_data}
    
    selected_polygon_name = st.selectbox(
        "选择一个面要素",
        list(polygon_options.keys()),
        help="选择一个面要素来查看包含在其内的所有点要素"
    )
    
    if selected_polygon_name:
        polygon_id = polygon_options[selected_polygon_name]
        
        # 获取选中的面要素详情
        polygon_data = spatial_repo.get_polygon_by_id(polygon_id)
        
        # 查询包含在面内的点要素
        with st.spinner("正在查询包含的点要素..."):
            points_in_polygon = spatial_repo.get_points_in_polygon(polygon_id)
        
        # 渲染可视化
        spatial_viz.render_points_in_polygon_demo(polygon_data, points_in_polygon)
        
        # 显示SQL查询说明
        with st.expander("📝 SQL查询说明"):
            st.code("""
-- 使用PostGIS的ST_Contains函数查询点在面内的空间关系
SELECT p.*, ST_AsGeoJSON(p.geometry) as geometry
FROM spatial_points p
JOIN spatial_polygons poly ON ST_Contains(poly.geometry, p.geometry)
WHERE poly.id = {polygon_id}
ORDER BY p.name;
            """.format(polygon_id=polygon_id), language='sql')
            
            st.markdown("""
            **空间关系说明**:
            - `ST_Contains(A, B)`: 判断几何体A是否完全包含几何体B
            - 返回布尔值，true表示B完全在A的内部
            - 这是PostGIS中最常用的空间关系函数之一
            """)








def render_educational_institutions_demo(spatial_repo: SpatialRepository, spatial_viz: SpatialVisualization):
    """渲染区域内教育机构查询演示"""
    st.header("🏫 区域内教育机构查询")
    st.markdown("""
    本演示展示如何查询与各个空间区域相交的教育机构，使用PostGIS的`ST_Intersects`函数
    来识别位于不同区域内的教育设施。
    """)
    
    try:
        # 获取与各区域相交的教育机构
        institutions_by_region = spatial_repo.get_educational_institutions_by_regions()
        
        if not institutions_by_region:
            st.warning("未找到任何与区域相交的教育机构")
            return
        
        st.subheader("📊 查询结果")
        
        # 统计信息
        total_regions = len(institutions_by_region)
        total_institutions = sum(len(data['institutions']) for data in institutions_by_region.values())
        
        col1, col2 = st.columns(2)
        with col1:
            st.metric("包含教育机构的区域数", total_regions)
        with col2:
            st.metric("教育机构总数", total_institutions)
        
        # 按区域显示教育机构列表
        for region_name, data in institutions_by_region.items():
            with st.expander(f"📍 {region_name} ({len(data['institutions'])}个教育机构)", expanded=True):
                region_info = data['region_info']
                st.markdown(f"**区域信息**: {region_info['description']} (类型: {region_info['region_type']}, 级别: {region_info['level']})")
                
                # 教育机构列表
                institutions_df = pd.DataFrame(data['institutions'])
                if not institutions_df.empty:
                    # 只显示描述性信息，不显示geometry
                    display_df = institutions_df[['name', 'description', 'category']].copy()
                    display_df.columns = ['机构名称', '描述', '类别']
                    st.dataframe(display_df, use_container_width=True)
        
        st.subheader("🗺️ 空间分布可视化")
        
        # 准备可视化数据
        all_points = []
        all_regions = []
        
        # 获取所有区域数据用于可视化
        regions = spatial_repo.get_all_regions()
        for region in regions:
            all_regions.append(region)
        
        # 收集所有教育机构点
        for data in institutions_by_region.values():
            all_points.extend(data['institutions'])
        
        if all_points and all_regions:
            spatial_viz.render_overview_map(
                points_data=all_points,
                polygons_data=[],
                regions_data=all_regions
            )
        
        st.markdown("""
        **空间关系说明**:
        - `ST_Intersects(geometry1, geometry2)`: 判断两个几何体是否有空间交集
        - 教育机构（点）与区域（面）相交表示该机构位于该区域内
        - 查询条件: `category LIKE '%教育%'` 筛选教育相关机构
        - 结果按区域分组显示，便于区域教育资源分析
        """)
        
    except Exception as e:
        st.error(f"查询教育机构时发生错误: {str(e)}")
        with st.expander("查看详细错误信息"):
            st.code(traceback.format_exc())


def render_region_gis_analysis_demo(spatial_repo: SpatialRepository, spatial_viz: SpatialVisualization):
    """渲染区域GIS分析演示
    
    Args:
        spatial_repo: 空间数据仓储
        spatial_viz: 空间数据可视化
    """
    st.header("📊 区域GIS分析")
    st.markdown("""
    本演示展示PostGIS的高级空间分析功能，包括区域面积计算、缓冲区分析、
    空间统计等GIS分析工具。
    """)
    
    # 获取所有区域
    with st.spinner("正在加载区域数据..."):
        regions_data = spatial_repo.get_all_regions()
    
    if not regions_data:
        st.warning("没有找到区域数据")
        return
    
    # 创建区域选择下拉框
    region_options = {f"{region['name']} ({region.get('region_type', '未分类')})": region['id'] 
                     for region in regions_data}
    
    selected_region_name = st.selectbox(
        "选择要分析的区域",
        list(region_options.keys()),
        key="gis_analysis_region_select"
    )
    
    if selected_region_name:
        selected_region_id = region_options[selected_region_name]
        
        # 获取区域的GIS分析数据
        with st.spinner("正在进行GIS分析..."):
            region_analysis_data = spatial_repo.get_region_gis_analysis(selected_region_id)
        
        if region_analysis_data:
            # 使用spatial_viz的方法渲染GIS分析
            spatial_viz.render_region_gis_analysis(region_analysis_data)
            
            # 添加导出功能
            st.subheader("📥 数据导出")
            col1, col2 = st.columns(2)
            
            with col1:
                if st.button("📄 导出为GeoJSON", key="export_geojson"):
                    with st.spinner("正在生成GeoJSON文件..."):
                        geojson_data = spatial_repo.export_region_as_geojson(selected_region_id)
                        if geojson_data:
                            st.download_button(
                                label="⬇️ 下载GeoJSON文件",
                                data=geojson_data,
                                file_name=f"region_{selected_region_id}_{region_analysis_data['name']}.geojson",
                                mime="application/json",
                                key="download_geojson"
                            )
                            st.success("GeoJSON文件已准备就绪！")
                        else:
                            st.error("导出GeoJSON失败")
            
            with col2:
                if st.button("🗺️ 导出为Shapefile", key="export_shapefile"):
                    with st.spinner("正在生成Shapefile文件..."):
                        try:
                            import zipfile
                            import glob
                            
                            shapefile_path = spatial_repo.create_shapefile_from_region(selected_region_id)
                            if shapefile_path:
                                # 创建ZIP文件包含所有Shapefile组件
                                zip_path = shapefile_path.replace('.shp', '.zip')
                                shapefile_dir = os.path.dirname(shapefile_path)
                                shapefile_base = os.path.splitext(os.path.basename(shapefile_path))[0]
                                
                                with zipfile.ZipFile(zip_path, 'w') as zipf:
                                    # 添加所有相关文件到ZIP
                                    for ext in ['*.shp', '*.shx', '*.dbf', '*.prj', '*.cpg']:
                                        for file_path in glob.glob(os.path.join(shapefile_dir, shapefile_base + ext[1:])):
                                            zipf.write(file_path, os.path.basename(file_path))
                                
                                # 读取ZIP文件内容
                                with open(zip_path, 'rb') as f:
                                    zip_data = f.read()
                                
                                st.download_button(
                                    label="⬇️ 下载Shapefile (ZIP)",
                                    data=zip_data,
                                    file_name=f"region_{selected_region_id}_{region_analysis_data['name']}.zip",
                                    mime="application/zip",
                                    key="download_shapefile"
                                )
                                st.success("Shapefile已准备就绪！")
                                
                                # 清理临时文件
                                import shutil
                                shutil.rmtree(shapefile_dir)
                            else:
                                st.error("导出Shapefile失败，可能需要安装fiona和shapely库")
                                st.info("请运行: pip install fiona shapely")
                        except ImportError:
                            st.error("导出Shapefile需要额外的库支持")
                            st.info("请运行: pip install fiona shapely")
                        except Exception as e:
                            st.error(f"导出Shapefile时发生错误: {str(e)}")
            
            # 导出格式说明
            with st.expander("ℹ️ 导出格式说明"):
                st.markdown("""
                ### 📄 GeoJSON格式
                - **用途**: Web地图应用、JavaScript库（如Leaflet、OpenLayers）
                - **特点**: 基于JSON的地理数据交换格式，易于阅读和处理
                - **包含数据**: 几何信息、属性数据、GIS分析结果
                
                ### 🗺️ Shapefile格式
                - **用途**: GIS软件（如QGIS、ArcGIS、MapInfo）
                - **特点**: 工业标准的矢量数据格式，广泛支持
                - **文件组成**: .shp（几何）、.dbf（属性）、.shx（索引）、.prj（投影）
                - **注意**: 字段名限制为10个字符，中文可能需要编码处理
                
                ### 📊 导出数据包含
                - 区域几何边界
                - 基本属性（名称、类型、级别等）
                - GIS分析结果（面积、周长、中心点坐标）
                """)
            
            # 显示PostGIS分析函数说明
            with st.expander("🔍 PostGIS分析函数说明"):
                st.markdown("""
                ### PostGIS空间分析函数
                
                ```sql
                -- 计算区域面积（平方米）
                SELECT ST_Area(geometry) as area_sqm FROM spatial_regions WHERE id = :region_id;
                
                -- 计算区域周长（米）
                SELECT ST_Perimeter(geometry) as perimeter_m FROM spatial_regions WHERE id = :region_id;
                
                -- 创建缓冲区（1000米）
                SELECT ST_Buffer(geometry, 1000) as buffer_geom FROM spatial_regions WHERE id = :region_id;
                
                -- 计算区域质心
                SELECT ST_Centroid(geometry) as centroid FROM spatial_regions WHERE id = :region_id;
                
                -- 计算边界框
                SELECT ST_Envelope(geometry) as bbox FROM spatial_regions WHERE id = :region_id;
                ```
                
                **函数说明:**
                - `ST_Area()`: 计算多边形的面积
                - `ST_Perimeter()`: 计算多边形的周长
                - `ST_Buffer()`: 创建几何体的缓冲区
                - `ST_Centroid()`: 计算几何体的质心
                - `ST_Envelope()`: 计算几何体的最小边界矩形
                """)
        else:
            st.error("无法获取区域GIS分析数据")


if __name__ == "__main__":
    render_postgis_demo()